package metodos;

import datos.Datos;
import datos.Regla;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


public class Prism extends Metodo {

    ArrayList<Regla> reglas;
    ArrayList<String> valoresClase;
    Algoritmo alg = new Algoritmo();
    Datos datos;
    boolean unir;
    boolean mostrarSINO;
    private double presicion;
    private List<String> seleccionados;

    public double getPresicion() {
        return presicion;
    }

    public void setPresicion(double presicion) {
        this.presicion = presicion;
    }

    public Prism(Datos datos, int presicion, Object seleccionados2[]) {
        this.datos = datos;
        valoresClase = new ArrayList<String>();
        reglas = new ArrayList<Regla>();
        unir = false;
        mostrarSINO = false;
        this.presicion = (double) presicion / 100;
        this.seleccionados= new ArrayList<String>();
        for (int i = 0; i < seleccionados2.length; i++) {
            this.seleccionados.add((String) seleccionados2[i]);
            
        }
        
    }

    @Override
    public void Calcular() {

        ArrayList<ArrayList<String>> lista = datos.getLista();


        String[][] matriz = new String[datos.getLista().size()][datos.getLista().get(0).size()];
        int filas = datos.getLista().size();
        int columnas = datos.getLista().get(0).size();
        for (int i = 0; i < filas; i++) {
            for (int j = 0; j < columnas; j++) {
                matriz[i][j] = datos.getLista().get(i).get(j);

            }

        }

        ArrayList<String> vector = new ArrayList();
        String claseS = datos.getNombreClase();
        int clase = 0;
        String[] titulos = datos.getTitulos();

        for (int i = 0; i < titulos.length; i++) {
            vector.add("" + titulos[i]);
            if (titulos[i].equals(claseS)) {
                clase = i;
            }

        }

        alg.añadirColumnas(vector);
        alg.prismAlgoritmo(matriz, clase, this.presicion);


    }

    @Override
    public String LeerReglas() {
        List reglas = alg.listarReglas();
        String mostrar = "";
        if (this.seleccionados.isEmpty()) {
            for (int i = 0; i < reglas.size(); i++) {
                mostrar+=reglas.get(i).toString()+"";
            }
        }else{
        for (int i = 0; i < reglas.size(); i++) {
                for (int j = 0; j < this.seleccionados.size(); j++) {
                if(reglas.get(i).toString().contains(this.datos.getNombreClase()+"="+seleccionados.get(j).toUpperCase())){
                    mostrar+=reglas.get(i).toString()+"";
                    j=this.seleccionados.size();
                }
                
                    
            }
                
            }
        }


        return mostrar;
    }

    @Override
    public String LeerReglasProlog() {
        return "";

    }

    @Override
    public Datos getDatos() {
        return datos;
    }
}
class Algoritmo {

    private ArrayList rDefinitivas;
    private ArrayList columnas;
    ArrayList<ReglaCompleta> Reglas = new ArrayList<ReglaCompleta>();
    CrearRegla Aux;

    public Algoritmo() {
        rDefinitivas = new ArrayList();
        columnas = new ArrayList();

    }

    public ArrayList listarReglas() {
        return this.rDefinitivas;
    }

    //metodo para la clasificacion de dominios en cualquier columna------------->
    public ArrayList buscarDominios(int cclase, Object[][] matrix) {
        //devuelve array al estilo disctinc del sql
        int i;
        ArrayList dom = new ArrayList();
        for (i = 0; i < matrix.length; i++) {
            if (!dom.contains(matrix[i][cclase])) {
                dom.add(matrix[i][cclase]);
            }
        }
        return dom;
    }

    //metodo que busca filas con reglas cumplidas
    public boolean buscarfila(int nFila, ArrayList arreglo) {
        boolean entra = false;
        Iterator iter = arreglo.iterator();
        while (iter.hasNext()) {
            int indice = (Integer) iter.next();
            if (indice == nFila) {
                return true;
            }
        }
        return entra;
    }

    public void añadirColumnas(ArrayList<String> arreglo) {
        columnas = arreglo;
    }

    //metodo para limpiar el array de reglas
    public void limpiarReglas() {
        for (int i = 0; i < rDefinitivas.size(); i++) {
            rDefinitivas.remove(i);
        }

    }

//metodo para crear todas las posibles combinaciones de atributo-valor--------->
    public ArrayList combinaciones(int ClasePrincipal, Object[][] matrizDatos) {

        ArrayList ListasCombinaciones = new ArrayList();
        //se busca los dominios para la columna de la clase elegida
        ArrayList dominiosClase = buscarDominios(ClasePrincipal, matrizDatos);
        ArrayList dominiosColumna;

        //Explora las columnas
        for (int colum = 0; colum < columnas.size(); colum++) {
            //se buscan los dominios para el resto de columnas (sin incluir la clase)
            if (colum != ClasePrincipal) {
                dominiosColumna = buscarDominios(colum, matrizDatos);

                for (int domClase = 0; domClase < dominiosClase.size(); domClase++) {
                    //por cada columna se crea una lista con las combinaciones dominio-valor
                    ListaEfectividad TablaCombinacion = new ListaEfectividad();
                    ArrayList<Efectividad> comb = new ArrayList();

                    //inicia el ciclo para evaluar todos los dominios de la columna
                    //y se determina la relacion entre el numero de casos positivos
                    //con el total de casos
                    for (int domCol = 0; domCol < dominiosColumna.size(); domCol++) {

                        double totalDominioCol = 0;
                        double coincidenciasClase = 0;
                        for (int i = 0; i < matrizDatos.length; i++) {
                            Object campoClase = matrizDatos[i][ClasePrincipal];
                            Object campoCol = matrizDatos[i][colum];
                            //cuenta el total de atributos (p)
                            if (campoCol.equals(dominiosColumna.get(domCol))) {
                                totalDominioCol++;
                                //cuenta el total de atributos que coincide con la clase evaluandose (t)
                                if (campoClase.equals(dominiosClase.get(domClase))) {
                                    coincidenciasClase++;
                                }
                            }
                        }
                        //para cada atributo evaluado se crea un registro de eficiencia
                        //y se almacena en un arreglo
                        double pre = coincidenciasClase / totalDominioCol;
                        Efectividad eficiencia = new Efectividad((String) dominiosColumna.get(domCol), pre, totalDominioCol);
                        comb.add(eficiencia);

                    }
                    //se guardan los datos para la tabla de combinaciones obtenida por cada columna
                    TablaCombinacion.setClase((String) dominiosClase.get(domClase));
                    TablaCombinacion.setColumna((String) columnas.get(colum));
                    TablaCombinacion.setRegEfectividad(comb);
                    //cada tabla se almacena en un arreglo para despues evaluar la precision y las reglas
                    ListasCombinaciones.add(TablaCombinacion);
                }
            }
        }
        return ListasCombinaciones;
    }

    //metodo par crear las reglas---------------------------------------------->
    public CrearRegla generarRegla(ArrayList tablasCombinacion, double precisionUsuario, String dominioClase, String clase) {

        CrearRegla regla = null;
        // se instancian los valores de referencia
        int indiceTablaMayor = 0;
        String campoMayor = "";
        String campoMayorTotal = "";
        String columnaMayorTotal = "";
        double numMayor = 0;
        double numMayorGral = 0;
        double mayorEfectividad = 0;
        double mayorEfectividadTotal = 0;
        boolean Entra = false;

        //inicia ciclo con la lista de combinaciones creadas
        for (int i = 0; i < tablasCombinacion.size(); i++) {
            ListaEfectividad subTabla = (ListaEfectividad) tablasCombinacion.get(i);
            //si el atributo evaluado en la subtabla es igual al dominio evaluado en la clase-> entra
            if (subTabla.getClase().equals(dominioClase)) {

                Entra = true;
                for (int j = 0; j < subTabla.getRegEfectividad().size(); j++) {
                    Efectividad efectividad = (Efectividad) subTabla.getRegEfectividad().get(j);
                    //se busca que la relacion p/t sea la mayor
                    if (efectividad.getPrecision() > mayorEfectividad) {
                        mayorEfectividad = efectividad.getPrecision();
                        indiceTablaMayor = i;
                        campoMayor = efectividad.getCampo();
                        numMayor = efectividad.getCont();
                    }
                }
                if (mayorEfectividad > mayorEfectividadTotal) {
                    mayorEfectividadTotal = mayorEfectividad;
                    campoMayorTotal = campoMayor;
                    columnaMayorTotal = subTabla.getColumna();
                    numMayorGral = numMayor;

                } else if (mayorEfectividad == mayorEfectividadTotal) {
                    if (numMayor > numMayorGral) {
                        mayorEfectividadTotal = mayorEfectividad;
                        campoMayorTotal = campoMayor;
                        columnaMayorTotal = subTabla.getColumna();

                    }
                }

                mayorEfectividad = 0;
            }
        }
        //validamos que todos los atributos hayan sido evaluados
        if (Entra) {

            //se evalua la precision, y se crea el estado de la regla
            if (mayorEfectividadTotal < precisionUsuario) {
                regla = new CrearRegla(columnaMayorTotal, campoMayorTotal, dominioClase, "incompleta", clase);
            } else {
                regla = new CrearRegla(columnaMayorTotal, campoMayorTotal, dominioClase, "completa", clase);
            }
            return regla;
        } else {
            return null;
        }

    }

    //metodos para validar reglas
    //ests devuelven una nueva matriz con valores depurados por reglas cumplidas
    public Object[][] ValidacionNormal(CrearRegla regla, Object[][] matriz, int clasePrincipal) {
        Object[][] nuevaMatriz = null;
        int indiceCol = 0;
        int indiceClase = clasePrincipal;
        int coincidencias = 0;
        ArrayList filasXeliminar = new ArrayList();
        //busca la columna donde se cumple la regla con precision del 1.0
        //y graba el indice de la columna
        for (int i = 0; i < columnas.size(); i++) {
            if (((String) columnas.get(i)).equals(regla.getColumna())) {
                indiceCol = i;
            }
        }
        /*aqui se buscan las filas que coincidan con las reglas
         si coinciden se añade a la lista de filas por eliminar*/
        for (int k = 0; k < matriz.length; k++) {
            Object campo = matriz[k][indiceCol];
            Object clase = matriz[k][indiceClase];
            if (((String) campo).equals(regla.getValor()) && ((String) clase).equals(regla.getConsecuente())) {
                coincidencias++;
                filasXeliminar.add(k);
            }
        }
        /*se inicializa la nueva matriz con el nuevo numero de filas que se consigue de restar
         al total de las filas originales las coincidencia*/
        nuevaMatriz = new Object[(matriz.length - coincidencias)][columnas.size()];
        int q = 0;
        /*recorre la matriz anterior para copiarlos en la nueva matriz omitiendo
         aquellos registros que cumplen con la regla*/
        for (int f = 0; f < matriz.length; f++) {
            if (!buscarfila(f, filasXeliminar)) {
                System.arraycopy(matriz[f], 0, nuevaMatriz[q], 0, columnas.size());
                q++;
            }
        }
        return nuevaMatriz;
    }

    //metodo para validar reglas cuando el estado de esta es incompleta
    public Object[][] validarParaIncompleta(CrearRegla regla, Object[][] matriz) {
        Object[][] nuevaMatriz = null;
        int indiceCol = 0;
        int coincidencias = 0;
        ArrayList filasAgregar = new ArrayList();
        //lo mismo que en el metodo validarNormal()
        //busca la columna donde se cumple la regla con precision del 1.0
        //y graba el indice de la columna
        for (int i = 0; i < columnas.size(); i++) {
            if (((String) columnas.get(i)).equals(regla.getColumna())) {
                indiceCol = i;
            }
        }
        for (int k = 0; k < matriz.length; k++) {
            Object campo = matriz[k][indiceCol];
            if (((String) campo).equals(regla.getValor())) {
                coincidencias++;
                filasAgregar.add(k);
            }
        }
        //a diferencia del metodo anterior aqui inicializa la nueva matriz con las coincidencias
        //y recorre la matriz orignal para copiar en la nueva matriz los valores que coinciden
        //con las reglas
        nuevaMatriz = new Object[coincidencias][columnas.size()];
        int q = 0;
        for (int f = 0; f < matriz.length; f++) {
            if (buscarfila(f, filasAgregar)) {
                System.arraycopy(matriz[f], 0, nuevaMatriz[q], 0, columnas.size());
                q++;
            }
        }
        return nuevaMatriz;
    }

    /*este metodo sirve para validar las reglas cuando estas son compuestas (de la forma Si regla1 && ... && reglaN -> Clase)*/
    public Object[][] validarReglaCompuesta(ReglaCompleta regla, Object[][] matriz, int clasePrincipal) {
        Object[][] nuevaMatriz = null;
        int indiceClase = clasePrincipal;
        int coincidentes = 0;
        int indiceCol = 0;
        int indiceCol2 = 0;
        ArrayList filasXeliminar = new ArrayList();
        /*el procedimieto aqui es como en los dos metodos anteriores (validar normal e incompleta
         pero aqui interviene otra columna y se evaluara en funcion de las 2 para luego
         proceder a eliminar las filas que cumplan con el criterio compuesto por las dos columnas*/
        for (int i = 0; i < columnas.size(); i++) {
            if (((String) columnas.get(i)).equals(regla.getColumna())) {
                indiceCol = i;
            }
            if (((String) columnas.get(i)).equals(regla.getColumna2())) {
                indiceCol2 = i;
            }
        }
        for (int k = 0; k < matriz.length; k++) {
            Object campo = matriz[k][indiceCol];
            Object campo2 = matriz[k][indiceCol2];
            Object clase = matriz[k][indiceClase];
            if (((String) campo).equals(regla.getValor()) && ((String) campo2).equals(regla.getValor2()) && ((String) clase).equals(regla.getConsecuente())) {
                coincidentes++;
                filasXeliminar.add(k);
            }
        }
//el procedimiento que sigue es como en el de validaNormal
        nuevaMatriz = new Object[(matriz.length - coincidentes)][columnas.size()];
        int q = 0;
        for (int f = 0; f < matriz.length; f++) {
            if (!buscarfila(f, filasXeliminar)) {
                System.arraycopy(matriz[f], 0, nuevaMatriz[q], 0, columnas.size());
                q++;
            }
        }
        return nuevaMatriz;
    }

    //entrando en materia este es el algoritmo que hace la evaluacion o prism
    public void prismAlgoritmo(Object[][] vistaMinable, int claseEscogida, double precisionGrado) {
        ArrayList dominiosClase = buscarDominios(claseEscogida, vistaMinable);
        ArrayList tablasCombinaciones;
        Object[][] matriz;
        String clase = (String) this.columnas.get(claseEscogida);
        //por si las moscas se limpia el vector de reglas
        this.limpiarReglas();
        //llenamos la matriz con los elmentos de la vista minable
        for (int i = 0; i < dominiosClase.size(); i++) {
            String dominioClase = (String) dominiosClase.get(i);
            matriz = vistaMinable;

            /*Esta parte quiere decir que mientras haya dominios sin evaluar en el array buscardominio
             se ejcutara la sentencia, (1) si existe una regla y su estado es completa entonces
             añadira esa regla a la lista de reglas finales y creara la nueva matriz sin los atributos
             que cumplen la regla; (2) si la regla es incompleta entonces llenara otra matriz y creara
             las combinaciones atributo-valor para esa segunda matriz para crear la segunda regla, añadirla
             a la regla anterior, armar la nueva regla y añadirla al array de reglasFinales
             (3) si la regla es nula o no hay se sale del ciclo*/
            while (((ArrayList) buscarDominios(claseEscogida, matriz)).size() > 1) {
                tablasCombinaciones = combinaciones(claseEscogida, matriz);
                CrearRegla regla = generarRegla(tablasCombinaciones, precisionGrado, dominioClase, clase);
                //(1)
                if (regla != null) {
                    if (regla.getEstado().equals("completa")) {
                        rDefinitivas.add(regla);
                        CrearRegla regla2 = new CrearRegla();
                        ReglaCompleta reglaFinal = new ReglaCompleta(regla, regla2, clase);
                        Reglas.add(reglaFinal);
                        matriz = ValidacionNormal(regla, matriz, claseEscogida);
                    } else if (regla.getEstado().equals("incompleta")) {
                        //(2)
                        Object[][] matriz2;
                        matriz2 = validarParaIncompleta(regla, matriz);
                        tablasCombinaciones = combinaciones(claseEscogida, matriz2);
                        CrearRegla regla2 = generarRegla(tablasCombinaciones, precisionGrado, dominioClase, clase);
                        ReglaCompleta reglaFinal = new ReglaCompleta(regla, regla2, clase);
                        rDefinitivas.add(reglaFinal);
                        Reglas.add(reglaFinal);
                        matriz = validarReglaCompuesta(reglaFinal, matriz, claseEscogida);
                    }
                } else if (regla == null) {
                    //(3)
                    break;
                }

            }
        }

    }

    public ArrayList<ReglaCompleta> getReglas() {
        return Reglas;
    }
}

class CrearRegla {

    protected String columna;
    protected String valor;
    protected String predicado;//clase que aprende
    protected String estado;//Completa o Incompleta
    protected String clase;

    public String getClase() {
        return clase;
    }

    public void setClase(String clase) {
        this.clase = clase;
    }

    public String getColumna() {
        return columna;
    }

    public void setColumna(String columna) {
        this.columna = columna;
    }

    public String getValor() {
        return valor;
    }

    public void setValor(String valor) {
        this.valor = valor;
    }

    public String getConsecuente() {
        return predicado;
    }

    /**
     * @param consecuente the consecuente to set
     */
    public void setConsecuente(String consecuente) {
        this.predicado = consecuente;
    }

    /**
     * @return the estado
     */
    public String getEstado() {
        return estado;
    }

    /**
     * @param estado the estado to set
     */
    public void setEstado(String estado) {
        this.estado = estado;
    }

    public CrearRegla(String columna, String valor, String predicado, String estado, String clase) {
        this.columna = columna;
        this.valor = valor;
        this.predicado = predicado;
        this.estado = estado;
        this.clase = clase;
    }

    public CrearRegla() {
        this.columna = "";
        this.valor = "";
        this.predicado = "";
        this.estado = "";
        this.clase = "";
    }

    @Override
    public String toString() {
        return "Si " + columna + "=" + valor + " ==> " + clase + "=" + predicado + "\n";
    }
}

class ReglaCompleta extends CrearRegla {

    private String columna2 = null;
    private String valor2 = null;

    public ReglaCompleta(CrearRegla regla1, CrearRegla regla2, String clase) {
        super(regla1.getColumna(), regla1.getValor(), regla1.getConsecuente(), regla1.getEstado(), clase);
        //para Regla2
        columna2 = regla2.getColumna();
        valor2 = regla2.getValor();
    }

    public String getColumna2() {
        return columna2;
    }

    public void setColumna2(String columna2) {
        this.columna2 = columna2;
    }

    public String getValor2() {
        return valor2;
    }

    public void setValor2(String valor2) {
        this.valor2 = valor2;
    }

    @Override
    public String toString() {
        return "Si " + columna + "=" + valor + " && " + columna2 + "=" + valor2 + " ==> " + clase + "=" + predicado + "\n";
    }
}

class Efectividad {

    private String campo;
    private double precision;
    private double cont;

    public Efectividad() {
    }

    public Efectividad(String campo, double precision, double cont) {
        this.precision = precision;
        this.campo = campo;
        this.cont = cont;
    }

    public String getCampo() {
        return campo;
    }

    public void setCampo(String campo) {
        this.campo = campo;
    }

    public double getPrecision() {
        return precision;
    }

    public void setPrecision(double precision) {
        this.precision = precision;
    }

    public double getCont() {
        return cont;
    }

    public void setCont(double cont) {
        this.cont = cont;
    }
}

class ListaEfectividad {

    private ArrayList<Efectividad> RegEfectividad;
    private String columna;
    private String clase;//Campo que aprende.

    public ListaEfectividad() {
        RegEfectividad = new ArrayList();
    }

    public ArrayList<Efectividad> getRegEfectividad() {
        return RegEfectividad;
    }

    public void setRegEfectividad(ArrayList<Efectividad> RegEfectividad) {
        this.RegEfectividad = RegEfectividad;
    }

    public String getColumna() {
        return columna;
    }

    public void setColumna(String columna) {
        this.columna = columna;
    }

    public String getClase() {
        return clase;
    }

    /**
     * @param clase the clase to set
     */
    public void setClase(String clase) {
        this.clase = clase;
    }
}
